﻿using System;
using System.IO;
using System.Linq;
using System.Net;
using System.ServiceModel.Web;
using System.Xml;
using System.ServiceModel;
using System.ServiceModel.Channels;
#if ORCASB2
using HttpBodyFormatMessageProperty = System.ServiceModel.Channels.HttpBodyFormatMessageProperty; 
#endif

namespace dasBlog.Storage
{
    public class WebStorageNode : IWebStorageNode
    {
        StorageBus storageBus;
        
        public WebStorageNode()
        {
            this.storageBus = StorageBus.Current;            
        }

        
        private XmlReader GetSchemaInternal(string scopeid, string nodeName, string xsd)
        {
            Moniker uri = new Moniker(scopeid, new PathSegmentName(nodeName));
            IStorageNode node = storageBus.FindNode(uri);
            if (node != null)
            {
                var ut = new UriTemplate("/{scopeid}/{node}/");
                var ui = ut.BindByPosition(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri,
                                  scopeid, nodeName);
                return node.GetItemSchema(ui.ToString(), xsd).GetReaderAtBodyContents();
            }
            return null;
        }

        public Message Select(string scopeid, string nodeName)
        {
            var replyAction = OperationContext.Current.EndpointDispatcher.DispatchRuntime.Operations["Select"].ReplyAction;
            if (WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters.AllKeys.Contains("xsd") ||
                WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters[null] == "xsd")
            {
                return Message.CreateMessage(
                    OperationContext.Current.IncomingMessageVersion,
                    replyAction,
                    GetSchemaInternal(scopeid, nodeName, WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["xsd"]));
            }
            else
            {
                return Message.CreateMessage(
                    OperationContext.Current.IncomingMessageVersion,
                    replyAction,
                    SelectInternal(new Moniker(scopeid, new PathSegmentName(nodeName))));
            }
        }

        private XmlReader SelectInternal(Moniker moniker)
        {
            QueryDescription query = null;
            var queryParameters = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters;
            string format = queryParameters["format"];
            string pag = queryParameters["pag"];

            if (format == "json")
            {
                OperationContext.Current.OutgoingMessageProperties.Add(
                    HttpBodyFormatMessageProperty.Name,
                    new HttpBodyFormatMessageProperty(WebContentFormat.Json));
            }
            if (pag != null)
            {
                return Aggregate(moniker, pag);
            }
            else
            {
                string queryName = queryParameters["q"];
                if (queryName != null)
                {
                    query = new QueryDescription(
                        queryName,
                        (from string key in queryParameters.Keys where key != "q" && key != "format" select new QueryArgument(key, queryParameters[key])).ToArray());
                }

                IStorageNode node = storageBus.FindNode(moniker);
                if (node != null)
                {
                    var result = node.Select(
                        WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri.ToString(),
                        moniker, MonikerMatch.Exact, query, SerializationMode.Native);
                    if (result == null)
                    {
                        WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotFound;
                        WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
                        return null;
                    }
                    return result.GetReaderAtBodyContents();
                }
                else
                {
                    WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotFound;
                    WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
                    return null;
                }
            }            
        }

        public Message Subselect(string scopeid, string nodeName, string id, string substg)
        {
            var replyAction = OperationContext.Current.EndpointDispatcher.DispatchRuntime.Operations["Subselect"].ReplyAction;
            var result = SelectInternal(new Moniker(scopeid, new PathSegmentName(nodeName), id, new PathSegmentName(substg)));
            if ( result == null )
            {
                WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotFound;
                WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
                return Message.CreateMessage(OperationContext.Current.IncomingMessageVersion, replyAction);
            }
            else
            {
                return Message.CreateMessage(OperationContext.Current.IncomingMessageVersion,
                    replyAction,result);
            }
        }
        
        public Message GetItem(string scopeid, string nodeName, string id)
        {
            var replyAction = OperationContext.Current.EndpointDispatcher.DispatchRuntime.Operations["GetItem"].ReplyAction;
            string format = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["format"];
            if (format == "json")
            {
                OperationContext.Current.OutgoingMessageProperties.Add(
                    HttpBodyFormatMessageProperty.Name,
                    new HttpBodyFormatMessageProperty(WebContentFormat.Json));
            }
            Moniker moniker = new Moniker(scopeid, new PathSegmentName(nodeName), id);
            IStorageNode node = storageBus.FindNode(moniker);
            if (node != null)
            {
                if (node is IStreamStorageNode &&
                    !WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters.AllKeys.Contains("info"))
                {
                    var template = new UriTemplate("/bin/{scopeid}/{node}/{streamId}");
                    WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.MovedPermanently;
                    WebOperationContext.Current.OutgoingResponse.Location = template.BindByPosition(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri, scopeid, nodeName, id).ToString().TrimEnd('/');
                    WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
                    return Message.CreateMessage(OperationContext.Current.IncomingMessageVersion, replyAction);
                }
                var result = node.Get(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri.ToString(), moniker, SerializationMode.Native);
                if (result == null)
                {
                    WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotFound;
                    WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
                    return Message.CreateMessage(OperationContext.Current.IncomingMessageVersion, replyAction);
                }
                return Message.CreateMessage(
                    OperationContext.Current.IncomingMessageVersion,
                    replyAction,
                    result.GetReaderAtBodyContents());
            }
            else
            {
                WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotFound;
                WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
                return Message.CreateMessage(OperationContext.Current.IncomingMessageVersion, replyAction);
            }
        }

        public void DeleteItem(string scopeid, string nodeName, string id)
        {
            Moniker moniker = new Moniker(scopeid, new PathSegmentName(nodeName), id);
            IStorageNode node = storageBus.FindNode(moniker);
            if (node != null)
            {
                node.Delete(moniker);
            }
        }

        public void AddItem(string scopeid, string nodeName, XmlElement item)
        {
            var queryParameters = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters;
            string format = queryParameters["format"];
            if (format == "json")
            {
                OperationContext.Current.OutgoingMessageProperties.Add(
                    HttpBodyFormatMessageProperty.Name,
                    new HttpBodyFormatMessageProperty(WebContentFormat.Json));
            }
            Moniker moniker = new Moniker(scopeid, new PathSegmentName(nodeName));
            IStorageNode node = storageBus.FindNode(moniker);
            if ( node != null )
            {
                string location = node.Store(moniker, item, SerializationMode.Native).ToString();
                WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Created;
                WebOperationContext.Current.OutgoingResponse.Location = new Moniker(location).ToUri(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri).ToString();
            }
        }

        public void AddSubitem(string scopeid, string nodeName, string id, string substg, XmlElement xitem)
        {
            var queryParameters = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters;
            string format = queryParameters["format"];
            var moniker = new Moniker(scopeid, new PathSegmentName(nodeName), id, new PathSegmentName(substg));
            IStorageNode node = storageBus.FindNode(moniker);
            if (node != null)
            {
                string location = node.Store(moniker, xitem, SerializationMode.Native).ToString();
                WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Created;
                WebOperationContext.Current.OutgoingResponse.Location = new Moniker(location).ToUri(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri).ToString();
            }
        }

        public void UpdateItem(string scopeid, string nodeName, string id, XmlElement item)
        {
            var queryParameters = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters;
            string format = queryParameters["format"];
            var moniker = new Moniker(scopeid, new PathSegmentName(nodeName), id);
            IStorageNode node = storageBus.FindNode(moniker);
            if (node != null)
            {
                if (node is IStreamStorageNode &&
                    !WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters.AllKeys.Contains("info"))
                {
                    var template = new UriTemplate("/{scopeid}/{node}/{streamId}/binary/");
                    WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.MovedPermanently;
                    WebOperationContext.Current.OutgoingResponse.Location = template.BindByPosition(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri, scopeid, nodeName, id).ToString().TrimEnd('/');
                    WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
                    return;
                }
                node.Store(new Moniker(scopeid, new PathSegmentName(nodeName), id), item, SerializationMode.Native);
            }
        }


        public Stream GetStream(string scopeid, string nodeName, string streamId)
        {
            Moniker moniker = new Moniker(scopeid, new PathSegmentName(nodeName), streamId);
            IStreamStorageNode node = storageBus.FindNode(moniker) as IStreamStorageNode;
            if (node != null )
            {
                var result = node.GetStream(new StreamStorageGetRequest { itemId = moniker });
                if (result == null)
                {
                    WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotFound;
                    WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
                    return null;
                }
                else
                {
                    WebOperationContext.Current.OutgoingResponse.ContentType = result.StreamInfo.ContentType;
                }
                return result.Stream;
            }
            else
            {
                WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotFound;
                WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
                return null;
            }
        }

        public string SetStream(string scopeid, string nodeName, string streamId, Stream stream)
        {
            Moniker uri = new Moniker(scopeid, new PathSegmentName(nodeName), streamId);
            IStreamStorageNode node = storageBus.FindNode(uri) as IStreamStorageNode;
            if (node != null)
            {
                var result = node.SetStream(
                    new StreamStorageSetRequest 
                        {
                            moniker = uri,
                            contentType = WebOperationContext.Current.IncomingRequest.ContentType,
                            name = streamId,
                            Stream = stream
                        }
                    );
                if (result == null)
                {
                    WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotAcceptable;
                    WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
                    return null;
                }
                else
                {
                    WebOperationContext.Current.OutgoingResponse.ContentType = result.StreamInfo.ContentType;
                }
                return result.StreamInfo.Id.ToString();
            }
            else
            {
                WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotFound;
                WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
                return null;
            }
        }

        #region IWebStorageNode Members


        private XmlReader Aggregate(Moniker moniker, string propname)
        {
            var queryParameters = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters;
            string format = queryParameters["format"];
            if (format == "json")
            {
                OperationContext.Current.OutgoingMessageProperties.Add(
                    HttpBodyFormatMessageProperty.Name,
                    new HttpBodyFormatMessageProperty(WebContentFormat.Json));
            }
            IStorageNode node = storageBus.FindNode(moniker);
            if (node != null)
            {
                var result = node.Aggregate(
                    WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri.ToString(),
                    moniker, MonikerMatch.Exact, propname, SerializationMode.Native);
                if (result == null)
                {
                    WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotFound;
                    WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
                }
                return result.GetReaderAtBodyContents();
            }
            else
            {
                WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotFound;
                WebOperationContext.Current.OutgoingResponse.SuppressEntityBody = true;
                return null;
            }
        }

        #endregion
    }
}
